package com.unistec.fileserver.server.provider.ftp;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.csource.common.MyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.unistec.fileserver.client.entity.Resource;
import com.unistec.fileserver.server.biz.FileServerService;
import com.unistec.fileserver.server.enums.FileType;
import com.unistec.fileserver.server.provider.IResourceSearchProvider;
import com.unistec.fileserver.server.provider.IStorageProvider;
import com.unistec.framework.exception.NotifyException;
import com.unistec.framework.utils.FileUtils;
import com.unistec.framework.utils.Objects;
import com.unistec.framework.utils.SpringUtils;
import com.unistec.framework.utils.UUIDUtils;

/**
 * FTP存储提供者
 * 
 * @description
 * @author 胡晓光
 * @date 2021-9-29
 * @time 11:34:06
 *
 */
public class FTPStorageProvider implements IStorageProvider, IResourceSearchProvider {
	Logger logger = LoggerFactory.getLogger(FTPStorageProvider.class);

	static String LOCAL_CHARSET = "GBK";
	static String SERVER_CHARSET = "ISO-8859-1";

	FTPStorageConfig config;
	private List<FileType> fileTypes;

	public FTPStorageProvider() {
		this.config = SpringUtils.getBean(FTPStorageConfig.class);
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String folder, File f, String fileName) throws Exception {
		return uploadFile(paramHolder, folder, fileName, new FileInputStream(f));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, File f, String fileName) throws Exception {
		return uploadFile(paramHolder, null, fileName, new FileInputStream(f));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, File f) throws Exception {
		return uploadFile(paramHolder, null, f.getName(), new FileInputStream(f));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, MultipartFile file) throws Exception {
		return uploadFile(paramHolder, null, file.getOriginalFilename(), file.getInputStream());
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String folder, File f) throws Exception {
		return uploadFile(paramHolder, folder, f.getName(), new FileInputStream(f));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String folder, MultipartFile file) throws Exception {
		return uploadFile(paramHolder, folder, file.getOriginalFilename(), file.getInputStream());
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String filename, byte[] data) throws Exception {
		return uploadFile(paramHolder, null, filename, new ByteArrayInputStream(data));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String folder, String filename, byte[] data) throws Exception {
		return uploadFile(paramHolder, folder, filename, new ByteArrayInputStream(data));
	}

	@Override
	public Resource uploadFile(JSONObject paramHolder, String folder, String filename, InputStream in) throws Exception {
		FTPClient ftp = null;
		String extention = FileUtils.getUriExtention(filename);
		String filenameReplacement = UUIDUtils.get32UUID() + "." + extention;
		
		try {
			int reply;
			ftp = getStorageClient();
			reply = ftp.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftp.disconnect();
				throw new NotifyException("[Storage][FTP] Cannot connect remote ftp server[" + config.getHost() + "].");
			}

			// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
			if (FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))) {
				LOCAL_CHARSET = "UTF-8";
			}

			ftp.setControlEncoding(LOCAL_CHARSET);
			ftp.enterLocalPassiveMode();// 设置被动模式

			String fullPath = null;

			// 切换到上传目录
			if (Objects.nonNull(folder) && !ftp.changeWorkingDirectory(folder)) {
				// 如果目录不存在创建目录
				String[] dirs = folder.split("/");
				for (String dir : dirs) {
					if (null == dir || "".equals(dir))
						continue;
					if (!ftp.changeWorkingDirectory(dir)) {
						if (!ftp.makeDirectory(dir)) {
							throw new NotifyException("[Storage][FTP] Cannot create directory[" + folder + "] on ftp server[" + config.getHost() + "].");
						} else {
							ftp.changeWorkingDirectory(dir);
						}
					}
				}
				fullPath = !folder.endsWith("/") ? (folder + "/" + filenameReplacement) : folder + filenameReplacement;
			} else if (Objects.nonNull(folder)){
				fullPath = !folder.endsWith("/") ? (folder + "/" + filenameReplacement) : folder + filenameReplacement;
			} else {
				fullPath = filenameReplacement;
			}

			// 设置上传文件的类型为二进制类型
			ftp.setFileType(FTP.BINARY_FILE_TYPE);

			// 上传文件
			// 文件名转码
			if (!ftp.storeFile(filenameReplacement, in)) {
				throw new NotifyException("[Storage][FTP] Save file failed [" + ((folder == null) ? "/" : folder) + "] on ftp server[" + config.getHost() + "].");
			}

			Resource uf = null;
			if (paramHolder.containsKey("asyncUpload") && paramHolder.getBooleanValue("asyncUpload") && paramHolder.containsKey("resourceId")) {
				// 异步上传
				FileServerService fs = SpringUtils.getBean(FileServerService.class);
				uf = fs.selectById("resourceId");
				if (uf != null) {
					uf.setUrl(fullPath);
					uf.setForeignIdentity(fullPath);
					uf.setStorageType(this.getClass().getName());

					// 保存
					fs.updateById(uf);
				}
			} else {
				uf = new Resource();
				uf.setName(filename);
				uf.setFileId(UUIDUtils.get32UUID());
				uf.setUrl(fullPath);
				uf.setForeignIdentity(fullPath);
				uf.setStorageType(this.getClass().getName());
			}
			return uf;
		} catch (IOException e) {
			logger.error("[Storage][FTP] 上传失败", e);
			return null;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (Exception e) {
				}
			}
			if (ftp != null) {
				ftp.logout();

				if (ftp.isConnected()) {
					try {
						ftp.disconnect();
					} catch (IOException ioe) {
					}
				}
			}
		}
	}

	@Override
	public boolean deleteFile(JSONObject paramHolder, String url) throws Exception {
		boolean flag = false;
		FTPClient ftpClient = null;
		try {
			ftpClient = getStorageClient();

			String pathName = url.substring(0, url.lastIndexOf("/"));
			String fileName = url.substring(url.lastIndexOf("/") + 1);

			// 切换FTP目录
			ftpClient.changeWorkingDirectory(pathName);
			ftpClient.dele(fileName);
			flag = true;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ftpClient != null) {
				ftpClient.logout();
				if (ftpClient.isConnected()) {
					try {
						ftpClient.disconnect();
					} catch (IOException ioe) {
					}
				}
			}

		}
		return flag;
	}

	@Override
	public boolean existFile(JSONObject paramHolder, String fileName) throws Exception {
		boolean flag = false;

		FTPClient ftpClient = null;
		try {
			ftpClient = getStorageClient();
			FTPFile[] ftpFileArr = ftpClient.listFiles(fileName);
			if (ftpFileArr.length > 0) {
				flag = true;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (ftpClient != null) {
				ftpClient.logout();
				if (ftpClient.isConnected()) {
					try {
						ftpClient.disconnect();
					} catch (IOException ioe) {
					}
				}
			}

		}
		return flag;
	}

	@Override
	public boolean exceedQuota(JSONObject paramHolder, JSONObject params) {
		return false;
	}

	@Override
	public List<FileType> getFileTypes() {
		return fileTypes;
	}

	/**
	 * 获取StorageClient
	 * 
	 * @return
	 * @throws IOException
	 * @throws MyException
	 */
	public FTPClient getStorageClient() throws SocketException, IOException {
		FTPClient ftpClient = null;
		ftpClient = new FTPClient();
		ftpClient.connect(config.getHost(), config.getPort());// 连接FTP服务器
		ftpClient.login(config.getUsername(), config.getPassword());// 登陆FTP服务器
		if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
			logger.info("未连接到FTP，用户名或密码错误。");
			ftpClient.disconnect();
		} else {
			logger.info("FTP连接成功。");
		}
		return ftpClient;
	}

	@Override
	public List<Resource> searchResource(JSONObject params, PageInfo<Resource> pageInfo) {
		return null;
	}

	@Override
	public List<Resource> getResourceInfoByForeignStorageId(List<String> foreignIds) {
		return null;
	}

	@Override
	public List<Resource> getResourceInfoByUrl(List<String> urls) {
		return null;
	}

	@Override
	public Resource getResourceInfoByUnique(String unique) {
		return null;
	}

	@Override
	public InputStream getFileInputStream(Resource resource) {
		FTPClient ftpClient = null;
		File tempFile = null;
		String fileName = FileUtils.getUriFileName(resource.getUrl());
		try {
			ftpClient = getStorageClient();

			String folderPath = getFolderPath(resource.getUrl());

			if (Objects.nonNull(folderPath)) {
				ftpClient.changeWorkingDirectory(folderPath);// 转移到FTP服务器目录
			}

			// 判断连接是否异常
			int reply = ftpClient.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftpClient.disconnect();
				throw new NotifyException("[Storage][FTP] Cannot connect remote ftp server[" + config.getHost() + "].");
			}

			// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
			if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
				LOCAL_CHARSET = "UTF-8";
			}

			ftpClient.setControlEncoding(LOCAL_CHARSET);
			ftpClient.enterLocalPassiveMode();// 设置被动模式
			ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			FTPFile[] files = ftpClient.listFiles();

			for (FTPFile file : files) {
				if (file.getName().equals(fileName)) {
					ftpClient.enterLocalPassiveMode();
					tempFile = FileUtils.createTempFile(resource.getFilenameSuffix());
					
					try(OutputStream out = new FileOutputStream(tempFile)){
						IOUtils.copy(ftpClient.retrieveFileStream(new String(fileName.getBytes(LOCAL_CHARSET), SERVER_CHARSET)), out);
					}
				}
			}
			ftpClient.logout();
		} catch (Exception e) {
			logger.error("下载失败", e);
		} finally {
			// 关闭FTP
			if (null != ftpClient) {
				if (ftpClient.isConnected()) {
					try {
						ftpClient.disconnect();
					} catch (IOException ioe) {
					}
				}
			}
		}

		try {
			if (null != tempFile) {
				return new FileInputStream(tempFile);
			} else {
				throw new NotifyException("下载失败，文件不存在");
			}
		} catch (Exception e) {
			logger.error("下载失败", e);
			throw new NotifyException("下载失败，文件不存在");
		}
	}

	@Override
	public InputStream getFileInputStream(String path) {
		FTPClient ftpClient = null;
		File tempFile = null;
		String fileName = FileUtils.getUriFileName(path);
		String extention = FileUtils.getUriExtention(path);

		try {
			ftpClient = getStorageClient();

			// 检查路径
			String folderPath = getFolderPath(path);

			ftpClient.changeWorkingDirectory(folderPath);// 转移到FTP服务器目录

			// 判断连接是否异常
			int reply = ftpClient.getReplyCode();
			if (!FTPReply.isPositiveCompletion(reply)) {
				ftpClient.disconnect();
				throw new NotifyException("[Storage][FTP] Cannot connect remote ftp server[" + config.getHost() + "].");
			}

			// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
			if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
				LOCAL_CHARSET = "UTF-8";
			}

			ftpClient.setControlEncoding(LOCAL_CHARSET);
			ftpClient.enterLocalPassiveMode();// 设置被动模式
			ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
			FTPFile[] files = ftpClient.listFiles();

			for (FTPFile file : files) {
				if (file.getName().equals(fileName)) {
					ftpClient.enterLocalPassiveMode();
					tempFile = FileUtils.createTempFile(extention);

					byte[] buf = new byte[204800];
					try (InputStream ins = ftpClient.retrieveFileStream(new String(fileName.getBytes(LOCAL_CHARSET), SERVER_CHARSET)); FileOutputStream out = new FileOutputStream(tempFile)) {
						int bufsize = 0;
						while ((bufsize = ins.read(buf, 0, buf.length)) != -1) {
							out.write(buf, 0, bufsize);
						}
					}
					break;
				}
			}
			ftpClient.logout();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 关闭FTP
			if (null != ftpClient) {
				if (ftpClient.isConnected()) {
					try {
						ftpClient.disconnect();
					} catch (IOException ioe) {
					}
				}
			}
		}

		try {
			if (null != tempFile) {
				return new FileInputStream(tempFile);
			} else {
				throw new NotifyException("下载失败，文件不存在");
			}
		} catch (Exception e) {
			throw new NotifyException("下载失败，文件不存在");
		}
	}

	String getFolderPath(String url) {
		if (!url.contains("/")) {
			return null;
		} else {
			return url.substring(0, url.lastIndexOf("/"));
		}
	}
}
